package com.vt.common.security.mobile;

import cn.hutool.core.util.StrUtil;
import com.vt.admin.api.entity.LoginSms;
import com.vt.common.core.constant.CommonConstant;
import com.vt.common.core.constant.SecurityConstants;
import com.vt.common.security.exception.VerificationCodeFailException;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 手机号登录验证filter
 */
public class MobileAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    private static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile";
    private static final String SPRING_SECURITY_FORM_CODE_KEY = "code";
    private AuthenticationEntryPoint authenticationEntryPoint = new MobileAuthenticationEntryPoint();
    @Getter
    @Setter
    private String mobileParameter = SPRING_SECURITY_FORM_MOBILE_KEY;
    @Getter
    @Setter
    private String codeParameter = SPRING_SECURITY_FORM_CODE_KEY;
    @Getter
    @Setter
    private boolean postOnly = true;
    @Getter
    @Setter
    private AuthenticationEventPublisher eventPublisher;

    @Setter
    RedisTemplate redisTemplate;

    public MobileAuthenticationFilter() {
        super(new AntPathRequestMatcher(SecurityConstants.MOBILE_TOKEN_URL, "POST"));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {
        if (postOnly && !request.getMethod().equals(HttpMethod.POST.name())) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + request.getMethod());
        }

        Authentication authResult = null;
        try {

            String mobile = obtainMobile(request);
            String code = obtainCode(request);

            //校验验证码
            LoginSms ls =
                    (LoginSms) redisTemplate.opsForValue().get(CommonConstant.REDIS_LOGIN_SMS_PREFIX + ":" + mobile);

            if (!success(ls, code))
                throw new VerificationCodeFailException();

            mobile = mobile.trim();

            MobileAuthenticationToken mobileAuthenticationToken = new MobileAuthenticationToken(mobile);

            setDetails(request, mobileAuthenticationToken);

            authResult = this.getAuthenticationManager().authenticate(mobileAuthenticationToken);

            logger.debug("Authentication success: " + authResult);
            eventPublisher.publishAuthenticationSuccess(authResult);
            SecurityContextHolder.getContext().setAuthentication(authResult);

        } catch (Exception failed) {
            SecurityContextHolder.clearContext();
            logger.debug("Authentication request failed: " + failed);

            eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed),
                    new PreAuthenticatedAuthenticationToken("access-token", "N/A"));

            try {
                authenticationEntryPoint.commence(request, response,
                        new UsernameNotFoundException(failed.getMessage(), failed));
            } catch (Exception e) {
                logger.error("authenticationEntryPoint handle error:{}", failed);
            }
        }

        return authResult;
    }

    private boolean success(LoginSms ls, String code) {
        if (null == ls) {
            return false;
        }
        if (!StrUtil.equals(ls.getNo(), code))
            return false;

        if (System.currentTimeMillis() - ls.getT() > 1000 * 60 * 5) {
            return false;
        }
        return true;
    }

    private String obtainMobile(HttpServletRequest request) {
        return request.getParameter(mobileParameter);
    }

    private String obtainCode(HttpServletRequest request) {
        return request.getParameter(codeParameter);
    }

    private void setDetails(HttpServletRequest request,
                            MobileAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }
}

